/*
 * Package il.ac.biu.cs.grossmm.api.flow
 * File ModificationMapper.java
 * 
 * This is a part of presence service framework API. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.api.flow;

import il.ac.biu.cs.grossmm.api.OperationFailedException;
import il.ac.biu.cs.grossmm.api.data.Node;
import il.ac.biu.cs.grossmm.api.data.NodeType;
import il.ac.biu.cs.grossmm.api.data.Nodeset;
import il.ac.biu.cs.grossmm.api.data.Property;
import il.ac.biu.cs.grossmm.api.data.UpdateType;

import java.util.Iterator;
import java.util.Set;

/**
 * Provides auxilary methods to simplify inspection of data node modification
 * events
 */
public abstract class ModificationMapper<N> {
	final static Property[] EMPTY = new Property[0];

	/**
	 * Checks whether property of a given node has ben modified as the result of
	 * the last update
	 * 
	 * @param <N>
	 *            node type class
	 * @param <V>
	 *            value class
	 * @param node
	 *            ndoe of interest
	 * @param ins
	 *            event inspector to use to inspect this node
	 * @param property
	 *            the property of interest
	 * @return true iff whether property of a given node has ben modified as the
	 *         result of the last update
	 * @throws OperationFailedException
	 */
	public static <N, V> boolean hasChanged(Node<N> node,
		NodeEventInspector ins, Property<N, V> property)
		throws OperationFailedException
	{
		Object a = ins.getOldValue(node, property);
		Object b = ins.getValue(node, property);

		// non equal references refer to non equal objects in data nodes
		return a == b;
	}

	/**
	 * Get array of all modified properties in a given data node.
	 * 
	 * @param <N>
	 *            node type class
	 * @param node
	 *            the node of interest
	 * @param ins
	 *            event inspector to use to inspect this node
	 * @return array of all properties which has changed
	 * @throws OperationFailedException
	 */
	public static <N> Property[] getModifiedProps(Node<N> node,
		NodeEventInspector ins) throws OperationFailedException
	{
		return inspect(node, ins, 0, 0);
	}

	/**
	 * Get array of all properties which has been modified in at least one node
	 * in a given nodeset of a given node.
	 * 
	 * @param <N>
	 *            node type class
	 * @param node
	 *            the node of interest
	 * @param nodeset
	 *            the nodeset (of given node) of interest
	 * @param ins
	 *            event inspector to use to inspect this node
	 * @return array of all properties which has changed in at least one node in
	 *         the given nodeset of the node
	 * @throws OperationFailedException
	 */
	public static <N, S> Property[] getModifiedProps(Node<N> node,
		Nodeset<N, S> nodeset, NodeEventInspector ins)
		throws OperationFailedException
	{
		Set<Node<S>> subnodes =
			ins.getSubnodes(node, nodeset, UpdateType.CHANGED);
		return inspect(subnodes, nodeset.nestedNodeType(), ins, 0, 0);
	}

	
	/**
	 * Get array of all properties which has been modified in at least one node
	 * in a given nodeset of a given node.
	 * 
	 * @param <N>
	 *            node type class
	 * @param node
	 *            the node of interest
	 * @param nodeset
	 *            the nodeset (of given node) of interest
	 * @param ins
	 *            event inspector to use to inspect this node
	 * @return array of all properties which has changed in at least one node in
	 *         the given nodeset of the node
	 * @throws OperationFailedException
	 */
	@SuppressWarnings("unchecked")
	public static <N> Property[] getModifiedProps(Set<Node<N>> setOfNodes,
		NodeEventInspector ins)
		throws OperationFailedException
	{
		if( setOfNodes.isEmpty() )
			return EMPTY;
		
		Iterator<Node<N>> it = setOfNodes.iterator();
		
		Node<N> node = it.next();
		
		NodeType nt = node.getNodeType();
		
		
		int propertiesNumber = nt.propertiesNumber();
		
		Property[] props = new Property[propertiesNumber];
		
		for( int i=0; i<propertiesNumber; i++ ) {
			props[i] = nt.property(i);
		}
		
		int k = propertiesNumber-1;
		
		while(k != 0) {
			int j=k;
			
			// go through all properties which are not modified in the
			// already inspected nodes
			while( j-->0 ) {
				Property p = props[j];
				if( ins.getOldValue(node, p) != ins.getValue(node, p)) {
					// swap props[j] and props[k], move pointer to the left
					props[j] = props[k];
					props[k] = p;
					k--;
				}
			}
			
			// more nodes?
			if(! it.hasNext() )
				break;
			
			node = it.next();
		}
		
		if( k==0 )
			return props; // all props has been modified
		
		// fast-copy modified propertis
		int sz = propertiesNumber - k;
		Property[] ps = new Property[sz];
		System.arraycopy(props, k, ps, 0, sz);
		
		return ps;
	}
	
	@SuppressWarnings("unchecked")
	static <N> Property[] inspect(Node<N> node, NodeEventInspector ins, int i,
		int n) throws OperationFailedException
	{
		// recursion on properties

		NodeType<N> nt = node.getNodeType();

		// did we inspect all properies (rec. term. condition)
		if (nt.propertiesNumber() == i)
			if (n == 0)
				return EMPTY;
			else
				return new Property[n];

		Property p = nt.property(i);

		if (ins.getOldValue(node, p) == ins.getValue(node, p))
			// property modified
			return inspect(node, ins, i + 1, n);

		// property not modified
		Property[] ps = inspect(node, ins, i + 1, n + 1);
		
		// put the modified property to the array
		ps[n] = p;
		return ps;
	}

	@SuppressWarnings("unchecked")
	static <N, S> Property[] inspect(Set<Node<S>> subnodes,
		NodeType<S> nodeType, NodeEventInspector ins, int i, int n)
		throws OperationFailedException
	{
		// recursion on propertis

		// did we inspect all properies (rec. term. condition)
		if (nodeType.propertiesNumber() == i)
			if (n == 0)
				return EMPTY;
			else
				return new Property[n];

		Property p = nodeType.property(i);
		for (Node<S> node : subnodes) {
			if (ins.getOldValue(node, p) != ins.getValue(node, p)) {
				// Property modified
				Property[] ps = inspect(subnodes, nodeType, ins, i + 1, n + 1);
				
				// put the modified property to the array
				ps[n] = p;
				return ps;
			}
		}

		// Property not modified in any node in this set
		return inspect(subnodes, nodeType, ins, i + 1, n);
	}

}
